__author__ = 'Muscle'
import mui.InternalUse.clock as clock
import time
import math
import mui.muievent
import mui.InternalUse.muicommon as muicommon
from mui.muievent import MUIEventDispatcher
import mui.InternalUse.clock as clock

_STATE_ = muicommon.enum(Idle=0, Running=1, Stop = 2, Pause = 3)


def calAnimValue( p, vs, ve, animtype, **kwargs):
    if (animtype == 'linear'):
        val = vs + (ve - vs) * p
        if (type(vs) == int):
            return int(val)
        return val

    elif (animtype == 'spring'):
        amp = vs - ve
        val = 0
        if p < 0.5:
           val = ve + (0.5 - p) * amp * 2
        else:
           p -= 0.5
           val = float(ve - amp * math.exp(-8 * p) * math.sin(4 * math.pi * p) / (2 * math.pi))
        if (type(vs) == int):
           return int(val)
        return val
    elif (animtype == 'decay'):
         decayingRate = kwargs.get('decayingRate', 4.0)
         endRatio = kwargs.get('endRatio', math.pow(2.0, -decayingRate * 1.0))
         a = (ve - vs * endRatio) / (1.0 - endRatio)
         b = (vs - ve) / (1.0 - endRatio)
         val = a + b * math.pow(2.0, -decayingRate * p)
         if (type(vs) == int):
             return int(val)
         return val

    elif (animtype == 'quantum'):
        if (p < 0.5):
            return vs
        else:
            return ve
    else:
        val = vs + (ve - vs) * p
        return val



class AnimBase(MUIEventDispatcher):
    def __init__(self, timeSlice, valueSlice, totalTime, **kwargs):
        MUIEventDispatcher.__init__(self)
        self._lastTimeQuery = 0.0
        self._elapseTime = 0.0
        self._state = _STATE_.Idle
        self._bLoop = kwargs.get('bLoop', False)

        self.set(timeSlice, valueSlice, totalTime)
        if (kwargs.get('bStartNow', False)):
            self.start()

    def __float__(self):
        return self.value

    def __int__(self):
        return int(float(self))

    def __coerce__(self, other):
        return coerce(float(self), other)

    def __nonzero__(self):
        return float(self) != 0

    def __long__(self):
        return long(float(self))

    def __neg__(self):
        return -float(self)

    def set(self, timeSlice, valueSlice, totalTime):
        if (len(timeSlice) != len(valueSlice) or len(timeSlice) < 2 or timeSlice[0] != 0.0 or timeSlice[-1] != 1 or totalTime <= 0):
            raise ValueError
        for i in range(len(timeSlice)-1):
            if (timeSlice[i] >= timeSlice[i+1]):
                print 'timeSlice need to be increasing order!!'
                raise ValueError
        self._timeSlice = timeSlice
        self._valueSlice = valueSlice
        self._animTotalTime = totalTime

    def _calValue(self, p, vs, ve): # linear
        raise NotImplementedError

    
    def _updateTime(self):
        curtime = time.clock()
        dT = curtime - self._lastTimeQuery
        self._elapseTime += dT
        self._lastTimeQuery = curtime
        if (self._elapseTime > self._animTotalTime):
            if (not self._bLoop):
                self.stop()
                self.dispatch_event('Anim End', self)
            else:
                self._elapseTime = self._elapseTime % self._animTotalTime
                self.dispatch_event('Anim Loop', self)

    def queryValue(self):
        if (self._state == _STATE_.Running):
            self._updateTime()

        eT = self._elapseTime / self._animTotalTime
        if (eT >= 1.0):
            if (not self._bLoop):
                return self._valueSlice[-1]
            else:
                eT = eT % 1.0
        else:
            ts, te = 0.0, 0.0
            vs, ve = 0.0, 0.0
            for i in range(len(self._timeSlice)-1):
                if (self._timeSlice[i] <= eT) and (self._timeSlice[i+1] >= eT):
                    ts = self._timeSlice[i]
                    te = self._timeSlice[i+1]
                    vs = self._valueSlice[i]
                    ve = self._valueSlice[i+1]
                    break
            p = (eT - ts) / (te - ts)
            return self._calValue( p, vs, ve)

    value = property(queryValue)
    def start(self):
        if (self._state == _STATE_.Idle or self._state == _STATE_.Stop):
            self._elapseTime = 0
            self._lastTimeQuery = time.clock()
        elif (self._state == _STATE_.Pause):
            self._lastTimeQuery = time.clock()
        elif (self._state == _STATE_.Running):
            pass
        self._state = _STATE_.Running

    def stop(self):
        self._state = _STATE_.Stop

    def pause(self):
        self._state = _STATE_.Pause

    def reset(self):
        self._state = _STATE_.Idle
        self._elapseTime = 0
        self._lastTimeQuery = time.clock()


class AnimLinear(AnimBase):
     def __init__(self, timeSlice, valueSlice, totalTime, **kwargs):
        AnimBase.__init__(self, timeSlice, valueSlice, totalTime, **kwargs)

     def _calValue(self, p, vs, ve):
         return calAnimValue(p, vs, ve, 'linear')

class AnimSpring(AnimBase):
     def __init__(self, timeSlice, valueSlice, totalTime, **kwargs):
         AnimBase.__init__(self, timeSlice, valueSlice, totalTime, **kwargs)

     def _calValue(self, p, vs, ve):
         return calAnimValue(p, vs, ve, 'spring')

class AnimDecay(AnimBase):
     def __init__(self, timeSlice, valueSlice, totalTime, **kwargs):
         AnimBase.__init__(self, timeSlice, valueSlice, totalTime, **kwargs)
         self._decayingRate = kwargs.get('decayingRate', 4.0)
         self._endRatio = math.pow(2.0, -self._decayingRate * 1.0)
         
     def _calValue(self, p, vs, ve):
         return calAnimValue(p, vs, ve, 'decay', decayingRate = self._decayingRate, endRatio = self._endRatio)


class AnimQuantum(AnimBase):
    def __init__(self, timeSlice, valueSlice, totalTime, **kwargs):
        AnimBase.__init__(self, timeSlice, valueSlice, totalTime, **kwargs)

    def _calValue(self, p, vs, ve):
        return calAnimValue(p, vs, ve, 'quantum')
"""
class AnimTarget(AnimBase):
    def __init__(self, timeDuration, val_from, val_target, **kwargs):
        self._animType = kwargs.get('animtype', 'linear')
        timeDelay = kwargs.get('delay', 0.0)
        totalTime = timeDuration + timeDelay
        timeslice = []
        valueslice = []
        if (timeDelay > 0):

            timeslice = [0.0, timeDelay / totalTime, totalTime]
            valueslice = [val_from, val_from, val_target]
        else:
            timeslice = [0.0, totalTime]
            valueslice = [val_from, val_target]
        AnimBase.__init__(self, timeslice, valueslice, totalTime)

    def _calValue(self, p, vs, ve):
        return calAnimValue(p, vs, ve, self._animType)
"""
  

class AnimTask(MUIEventDispatcher):
    _runningTask = set()  # an space for animTask live after out of the scope

    def __init__(self, target, targetAttr, animObj, **kwargs):
        super(AnimTask, self).__init__()
        self._animObj = animObj
        self._target = target
        self._targetAttr = targetAttr
        self._state = _STATE_.Idle
        self._scheduleItem = None
        self.bAllowInterrupt = kwargs.get('bAllowInterrupt', False)
        self.autoRemove(self._animObj.bind('Anim End', self._onAnimObjEnd))

    def __del__(self):
        self.__doUnSchedule()

    def _onAnimObjEnd(self, animobj):
        self.stop()
        self.dispatch_event('AnimTask End', self)
        self.__OutRunningTask()

    def __doUnSchedule(self):
        if (self._scheduleItem):
            clock.unschedule_item(self._scheduleItem)
            self._scheduleItem = None

    def __doSchedule(self):
        self.__doUnSchedule()
        if (self.bAllowInterrupt):
            self._removeConflictTask()
        else:
            conflictTask = self._findConflictTask()
            if (conflictTask):
                return False
        self._scheduleItem = clock.schedule(self.update)
        return True

    def __InRunningTask(self):
        AnimTask._runningTask.add(self)

    def __OutRunningTask(self):
        try:
            AnimTask._runningTask.remove(self)
        except KeyError, e:
            pass

    def update(self):
        val = self._animObj.value
        setattr(self._target, self._targetAttr, val)

    def pause(self):
        self._animObj.pause()
        self.__doUnSchedule()
        self.__OutRunningTask()
        self._state = _STATE_.Pause

    def stop(self):
        self._animObj.stop()
        self.__doUnSchedule()
        self.__OutRunningTask()
        self._state = _STATE_.Stop

    def start(self):
        self._animObj.start()
        if (self._state != _STATE_.Running):
            if (not self.__doSchedule()):
                return False
            self._state = _STATE_.Running
            self.__InRunningTask()
        return True

    def __getState(self):
        return self._state

    state = property(__getState)

    def reset(self):
        self._animObj.reset()
        if (self._state == _STATE_.Running):
            self.__doUnSchedule()
            self._state = _STATE_.Idle
            self.__OutRunningTask()
            self.update()

    def _findConflictTask(self):
        conflictTask = [task for task in AnimTask._runningTask if (task != self and task._target == self._target and task._targetAttr == self._targetAttr)]
        return conflictTask

    def _removeConflictTask(self):
        conflictTask = self._findConflictTask()
        for t in conflictTask:
            t.stop()



def postExecute(func, *args, **kwargs):
    clock.postExecute(func, *args, **kwargs)

def schedule(func, *args, **kwargs):
    '''Schedule 'func' to be called every frame on the default clock.

    The arguments passed to func are ``dt``, followed by any ``*args`` and
    ``**kwargs`` given here.

    :Parameters:
        `func` : function
            The function to call each frame.
    '''
    return clock.schedule(func, *args, **kwargs)

def schedule_interval(func, interval, *args, **kwargs):
    '''Schedule 'func' to be called every 'interval' seconds on the default
    clock.

    The arguments passed to 'func' are 'dt' (time since last function call),
    followed by any ``*args`` and ``**kwargs`` given here.

    :Parameters:
        `func` : function
            The function to call when the timer lapses.
        `interval` : float
            The number of seconds to wait between each call.

    '''
    return clock.schedule_interval(func, interval, *args, **kwargs)

def schedule_interval_soft(func, interval, *args, **kwargs):
    '''Schedule 'func' to be called every 'interval' seconds on the default
    clock, beginning at a time that does not coincide with other scheduled
    events.

    The arguments passed to 'func' are 'dt' (time since last function call),
    followed by any ``*args`` and ``**kwargs`` given here.

    :see: `Clock.schedule_interval_soft`

    :since: pyglet 1.1

    :Parameters:
        `func` : function
            The function to call when the timer lapses.
        `interval` : float
            The number of seconds to wait between each call.

    '''
    return clock.schedule_interval_soft(func, interval, *args, **kwargs)

def schedule_once(func, delay, *args, **kwargs):
    '''Schedule 'func' to be called once after 'delay' seconds (can be
    a float) on the default clock.  The arguments passed to 'func' are
    'dt' (time since last function call), followed by any ``*args`` and
    ``**kwargs`` given here.

    If no default clock is set, the func is queued and will be scheduled
    on the default clock as soon as it is created.

    :Parameters:
        `func` : function
            The function to call when the timer lapses.
        `delay` : float
            The number of seconds to wait before the timer lapses.

    '''
    return clock.schedule_once(func, delay, *args, **kwargs)

def unschedule(func):
    '''Remove 'func' from the default clock's schedule.  No error
    is raised if the func was never scheduled.

    :Parameters:
        `func` : function
            The function to remove from the schedule.

    '''
    return clock.unschedule(func)

def unschedule_item(item):
    return clock.unschedule_item(item)